import Editor from "../../editor";
import { Operation, SetSelectionOperation } from "../../operations";

class UndoList {
  private _editor: Editor;

  // 最大撤销步数
  private MAX_UNDO_NUMBER: number = 30;
  // 撤销合并间隔，300ms内的一起撤销
  private PUSH_GAP_TIME: number = 300;
  // 撤销数组，选区也要撤回，独立的光标变化可以抛弃
  private _undoList: Array<Array<Operation>>;
  // 当前撤销数组位置
  private _currentPos: number = -1;
  // 上次插入时间
  private _lastPushTime: number = 0;

  constructor(editor: Editor) {
    this._undoList = new Array();
    this._editor = editor;
  }

  // 如果上一次操作数组中全部都是光标变化，则该操作舍弃
  _checkAllSelectionOperation(): void {
    const curOperations: Array<Operation> | undefined = this._undoList[this._undoList.length - 1];
    if (curOperations == null) return;

    let allSelections = true;
    for (let i: number = 0; i < curOperations.length; i++) {
      if (!(curOperations[i] instanceof SetSelectionOperation)) {
        allSelections = false;
        break;
      }
    }
    if (allSelections) {
      this._currentPos = this._currentPos === this._undoList.length - 1 ? this._currentPos - 1 : this._currentPos;
      this._undoList.pop();
    }
  }

  // 如果当前撤销指针不在数组末尾，则之后的操作全部舍弃
  _checkIsUndoing() : void {
    if (this._undoList.length !== 0 && this._currentPos !== this._undoList.length - 1) {
      if (this._currentPos <= -1) {
        this._undoList = new Array();
      } else {
        this._undoList = this._undoList.slice(0, this._currentPos + 1);
      }
    }
  }

  pushOperation(operation: Operation): void {
    const curTime: number = Date.now();
    // 如果在300ms内操作，直接归入当前撤销数组
    if (curTime - this._lastPushTime <= this.PUSH_GAP_TIME) {
      this._undoList[this._undoList.length - 1].push(operation);
      if (!(operation instanceof SetSelectionOperation) && 
        this._undoList.length !== 0 && this._currentPos !== this._undoList.length - 1) {
        // 如果当前撤销指针不在数组末尾，则之后的操作全部舍弃
        const temp = this._undoList[this._undoList.length - 1].slice();
        this._checkIsUndoing();
        this._undoList.push(temp);
        this._currentPos = this._undoList.length - 1;
      }
    } else {
      // 如果上一次操作数组中全部都是光标变化，则该操作舍弃
      this._checkAllSelectionOperation();

      // 如果当前撤销数组已满，将最早记录的操作出列
      if (this._undoList.length >= this.MAX_UNDO_NUMBER) {
        this._undoList.shift();
        this._currentPos = this._currentPos - 1;
      }

      if (operation instanceof SetSelectionOperation) {
        this._undoList.push([operation]);
      } else {
        this._checkIsUndoing();
        this._undoList.push([operation]);
        this._currentPos = this._undoList.length - 1;
      }
    }

    this._lastPushTime = curTime;
    // console.log(this._undoList);
  }

  undoOperation() {
    this._checkAllSelectionOperation();

    const curOperations: Array<Operation> = this._undoList[this._currentPos];
    if (curOperations == null || curOperations.length === 0) {
      return;
    }

    for (let i: number = curOperations.length - 1; i >= 0; i--) {
      const operations: Operation[] = curOperations[i].reverse();
      for (let j: number = 0; j < operations.length; j++) {
        operations[j].apply(this._editor);
      }
    }

    this._currentPos = this._currentPos - 1;
  }

  redoOperstion() {
    if (this._undoList.length === 0 || this._currentPos >= this._undoList.length - 1) {
      return;
    }

    const curOperations: Array<Operation> = this._undoList[this._currentPos + 1];
    if (curOperations == null || curOperations.length === 0) {
      return;
    }

    for (let i: number = 0; i < curOperations.length; i++) {
      curOperations[i].apply(this._editor);
    }
    this._currentPos = this._currentPos + 1;
  }
}

export default function WithUndoRedo (editor: Editor) {
  const undoList = new UndoList(editor);

  const apply = editor.apply.bind(editor);
  editor.apply = (operation: Operation) => {
    undoList.pushOperation(operation);
    apply(operation);
  }

  // 撤销
  const undo = editor.undo.bind(editor);
  editor.undo = () => {
    undoList.undoOperation();
    undo();
  }

  // 回退
  const redo = editor.redo.bind(editor);
  editor.redo = () => {
    undoList.redoOperstion();
    redo();
  }
  return editor;
}